#pragma once
#include "AutoConfigWebServer.h"
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
#include <string>
// 设置心跳值30s
#define KEEPALIVEATIME 30 * 1000

#define server_ip "bemfa.com" // 巴法云服务器地址默认即可
#define tcp_server_port 8344  // 服务器端口，tcp创客云端口8344
#define matt_server_port 9501
#define mattssl_server_port 9503 // TLS1.2

using std::placeholders::_1;
using std::placeholders::_2;
using std::placeholders::_3;

using MSG_CALLBACK_FUN = std::function<void(const char *, const char *, unsigned int)>;

class ESP8266BafayunBase
{
public:
    virtual void autoConnect() = 0;
    virtual void init() = 0;
    virtual void upload(String _msg) = 0;
    virtual void loop();
    virtual void setMsgCallback(MSG_CALLBACK_FUN fun) = 0;
};

class ESP8266BafayunTCP : public ESP8266BafayunBase
{
private:
    ESP8266BafayunTCP(){};
    CAutoConnect m_autoConnect;
    // tcp客户端相关初始化，默认即可
    WiFiClient TCPclient;
    String TcpClient_Buff = ""; // 初始化字符串，用于接收服务器发来的数据
    MSG_CALLBACK_FUN msgcallback;
    unsigned long TcpClient_preTick = 0;
    unsigned long preHeartTick = 0;    // 心跳
    unsigned long preTCPStartTick = 0; // 连接
    bool preTCPConnected = false;

    String uid, topic;

    void startTCPClient()
    {
        if (TCPclient.connect(server_ip, tcp_server_port))
        {
            Serial.print("\nConnected to server:");
            Serial.printf("%s:%d\r\n", server_ip, tcp_server_port);

            String tcpTemp = "";                                       // 初始化字符串
            tcpTemp = "cmd=1&uid=" + uid + "&topic=" + topic + "\r\n"; // 构建订阅指令
            sendtoTCPServer(tcpTemp);                                  // 发送订阅指令
            tcpTemp = "";                                              // 清空
            /*
             //如果需要订阅多个主题，可再次发送订阅指令
              tcpTemp = "cmd=1&uid="+UID+"&topic="+主题2+"\r\n"; //构建订阅指令
              sendtoTCPServer(tcpTemp); //发送订阅指令
              tcpTemp="";//清空
             */

            preTCPConnected = true;
            preHeartTick = millis();
            TCPclient.setNoDelay(true);

            recvformserver();
            // 订阅成功则拉取上一的消息，比如断线以后可能发送了关闭/打开指令
            if (TcpClient_Buff.equals("cmd=1&res=1") == 0)
            {
                getlastmsg();
            }
            TcpClient_Buff.clear();
        }
        else
        {
            Serial.print("Failed connected to server:");
            Serial.println(server_ip);
            TCPclient.stop();
            preTCPConnected = false;
        }
        preTCPStartTick = millis();
    }

    void recvformserver()
    {
        // 检查是否断开，断开后重连
        if (WiFi.status() != WL_CONNECTED)
            return;
        if (!TCPclient.connected())
        { // 断开重连
            if (preTCPConnected == true)
            {
                preTCPConnected = false;
                preTCPStartTick = millis();
                Serial.println();
                Serial.println("TCP 中断");
                TCPclient.stop();
            }
            else if (millis() - preTCPStartTick > 1 * 1000) // 重新连接
            {
                Serial.println("TCP 重连");
                startTCPClient();
            }
        }
        else
        {
            if (TCPclient.available())
            {
                // 收数据
                Serial.println("收数据");
                TcpClient_Buff = TCPclient.readStringUntil('\n');
                preHeartTick = millis();
                Serial.println("收数据完成");
                Serial.println(TcpClient_Buff);
            }
        }
    }

    void parsemsg(String &getTopic, String &getMsg)
    {
        if (TcpClient_Buff.length() > 15)
        {
            // 此时会收到推送的指令，指令大概为 cmd=2&uid=xxx&topic=light002&msg=off
            int topicIndex = TcpClient_Buff.indexOf("&topic=") + 7;
            int msgIndex = TcpClient_Buff.indexOf("&msg=");
            getTopic = TcpClient_Buff.substring(topicIndex, msgIndex);
            getMsg = TcpClient_Buff.substring(msgIndex + 5);
        }
        TcpClient_Buff.clear();
    }

    int sendtoTCPServer(String p)
    {
        if (!TCPclient.connected())
        {
            Serial.println("Client is not readly");
            return 0;
        }
        return TCPclient.print(p);
    }

    void keepalive()
    {
        if (millis() - preHeartTick >= KEEPALIVEATIME)
        {
            // 保持心跳
            preHeartTick = millis();
            Serial.println("--Keep alive:");
            sendtoTCPServer("ping\r\n"); // 发送心跳，指令需\r\n结尾，详见接入文档介绍
        }
    }

    void getlastmsg()
    {
        String upstr = "";
        upstr = "cmd=3&uid=" + uid + "&topic=" + topic + "\r\n";
        sendtoTCPServer(upstr);
        Serial.println("getlastmsg....");
        Serial.println(upstr);
    }

public:
    static ESP8266BafayunTCP &getSingleton()
    {
        static ESP8266BafayunTCP _singleton;
        return _singleton;
    }

    virtual void autoConnect() override
    {
        m_autoConnect.AutoConnect();
    }

    void init(const String &_uid, const String &_topic)
    {
        if (_uid.isEmpty() || _topic.isEmpty())
        {
            Serial.println("非法的key和topic");
            // 进入配网，这个可以通过网页设置
            m_autoConnect.HtmlConfig();
            init();
            return;
        }

        uid = _uid;
        topic = _topic;

        autoConnect();

        Serial.println("");
        Serial.println("WiFi connected");
        Serial.print("IP address: ");
        Serial.println(WiFi.localIP());
        startTCPClient();
    }

    virtual void init() override
    {
        const CConfigMgr &configmgr = CConfigMgr::getSingleton();
        init(configmgr.KEY(), configmgr.TOPIC());
    }

    virtual void upload(String _msg) override
    {
        String upstr = "";
        upstr = "cmd=2&uid=" + uid + "&topic=" + topic + "&msg=on#" + _msg + "\r\n";
        sendtoTCPServer(upstr);
        Serial.println("upload....");
        Serial.println(upstr);
        preHeartTick = millis();
    }

    virtual void loop() override
    {
        keepalive();
        // 从服务器接收消息
        recvformserver();
        // 解析接收到的消息
        String msg, topic;
        parsemsg(topic, msg);
        if (msgcallback)
        {
            msgcallback(topic.c_str(), msg.c_str(), msg.length());
        }
    }

    virtual void setMsgCallback(MSG_CALLBACK_FUN fun) override
    {
        msgcallback = fun;
    }
};

class ESP8266BafayunMQTT : public ESP8266BafayunBase
{

private:
    ESP8266BafayunMQTT(){};
    CAutoConnect m_autoConnect;
    // tcp客户端相关初始化，默认即可
    WiFiClient TCPclient;
    PubSubClient client;
    String uid, topic;
    MSG_CALLBACK_FUN msgcallback;

    void startTCPClient()
    {
        client.setClient(TCPclient);
        client.setServer(server_ip, matt_server_port);
        client.setCallback(std::bind(&ESP8266BafayunMQTT::callback, this, _1, _2, _3));
        client.connect(uid.c_str());
        client.subscribe(topic.c_str());
    }

    void callback(char *topic, uint8_t *payload, unsigned int len)
    {
        if (msgcallback)
        {
            msgcallback((const char *)topic, (const char *)payload, len);
        }
    }

public:
    static ESP8266BafayunMQTT &getSingleton()
    {
        static ESP8266BafayunMQTT _singleton;
        return _singleton;
    }

    virtual void setMsgCallback(MSG_CALLBACK_FUN fun) override
    {
        msgcallback = fun;
    }

    virtual void autoConnect() override
    {
        m_autoConnect.AutoConnect();
    }

    void init(const String &_uid, const String &_topic)
    {
        if (_uid.isEmpty() || _topic.isEmpty())
        {
            Serial.println("非法的key和topic");
            // 进入配网，这个可以通过网页设置
            m_autoConnect.HtmlConfig();
            init();
            return;
        }

        uid = _uid;
        topic = _topic;

        autoConnect();

        Serial.println("");
        Serial.println("WiFi connected");
        Serial.print("IP address: ");
        Serial.println(WiFi.localIP());
        startTCPClient();
    }

    virtual void init() override
    {
        const CConfigMgr &configmgr = CConfigMgr::getSingleton();
        init(configmgr.KEY(), configmgr.TOPIC());
    }

    virtual void upload(String _msg) override
    {
        static String unloadTopic=topic+"/set";
        client.publish(unloadTopic.c_str(), _msg.c_str());
    }

    virtual void loop() override
    {
        if (client.connected())
        {
            client.loop();
        }
        else
        {
            client.connect(uid.c_str());
            client.subscribe(topic.c_str());
        }
    }
};